﻿using Microsoft.Extensions.DependencyInjection;
using PmSoft.Caching;
using System.Collections.Concurrent;
using System.Text;

namespace PmSoft
{
    [UseDI(ServiceLifetime.Scoped)]
    public class EntityCacheService<TEntity>
    {
        private static readonly object lockObj = new object();
        private readonly ConcurrentDictionary<string, EntityCache> entityCaches;
        private readonly ICacheService cacheService;
        private readonly SemaphoreSlim semaphoreSlim = new SemaphoreSlim(initialCount: 1, maxCount: 1);
        private const string entityCachePrefix = "Entitys";

        public EntityCacheService(ICacheService cacheService)
        {
            this.cacheService = cacheService;
            this.entityCaches = new ConcurrentDictionary<string, EntityCache>();
        }

        public bool EnableDistributedCache { get => cacheService.EnableDistributedCache; }

        /// <summary>
        /// 获取实体缓存版本信息
        /// </summary>
        /// <returns></returns>
        public EntityCache GetEntityCache()
        {
            var entityData = EntityData.ForType(typeof(TEntity), EnableDistributedCache);

            if (!EnableDistributedCache)
                return entityData.EntityCache ?? entityData.ParseEntityCache();

            var entityCache = GetDistributedEntityCache(entityData.TypeHashID, () => entityData.ParseEntityCache());

            return entityCache;
        }

        /// <summary>
        /// 获取实体缓存版本信息
        /// </summary>
        /// <returns></returns>
        public async Task<EntityCache> GetEntityCacheAsync()
        {
            var entityData = EntityData.ForType(typeof(TEntity), EnableDistributedCache);

            if (!EnableDistributedCache)
                return entityData.EntityCache ?? entityData.ParseEntityCache();

            var entityCache = await GetDistributedEntityCacheAsync(entityData.TypeHashID, () => entityData.ParseEntityCache());

            return entityCache;
        }

        private EntityCache GetDistributedEntityCache(string typeHashID, Func<EntityCache> parseFunc)
        {
            semaphoreSlim.Wait();
            try
            {
                if (entityCaches.TryGetValue(typeHashID, out EntityCache? entityCache))
                    return entityCache;

                //从分布式缓存获取RealTimeCacheHelper对象
                string cacheKey = GetCacheKeyOfEntityCache(typeHashID);
                entityCache = cacheService.GetFromFirstLevel<EntityCache>(cacheKey);
                if (entityCache == null)
                {
                    entityCache = parseFunc();
                    cacheService.Set(cacheKey, entityCache, CachingExpirationType.Invariable);
                }
                entityCaches.TryAdd(typeHashID, entityCache);
                return entityCache;
            }
            finally
            {
                semaphoreSlim.Release();
            }
        }

        private async Task<EntityCache> GetDistributedEntityCacheAsync(string typeHashID, Func<EntityCache> parseFunc)
        {
            await semaphoreSlim.WaitAsync(5000);
            try
            {
                if (entityCaches.TryGetValue(typeHashID, out EntityCache? entityCache))
                    return entityCache;

                //从分布式缓存获取RealTimeCacheHelper对象
                string cacheKey = GetCacheKeyOfEntityCache(typeHashID);
                entityCache = await cacheService.GetFromFirstLevelAsync<EntityCache>(cacheKey);
                if (entityCache == null)
                {
                    entityCache = parseFunc();
                    await cacheService.SetAsync(cacheKey, entityCache, CachingExpirationType.Invariable);
                }
                entityCaches.TryAdd(typeHashID, entityCache);
                return entityCache;
            }
            finally
            {
                semaphoreSlim.Release();
            }
        }

        /// <summary>
        /// 获取列表缓存区域版本号
        /// </summary>
        /// <param name="propertyName">分区属性名称</param>
        /// <param name="propertyValue">分区属性值</param>
        /// <returns>分区属性的缓存版本（从0开始）</returns>
        public int GetAreaVersion(string? propertyName, object? propertyValue)
        {
            var entityCache = GetEntityCache();
            int num = 0;
            if (!string.IsNullOrEmpty(propertyName) && propertyValue != null)
            {
                propertyName = propertyName.ToLower();
                lock (entityCache.AreaVersionDictionary)
                {
                    if (entityCache.AreaVersionDictionary.TryGetValue(propertyName, out Dictionary<int, int>? dictionary))
                    {
                        dictionary.TryGetValue(propertyValue.GetHashCode(), out num);
                    }
                }
            }
            return num;
        }

        /// <summary>
        /// 获取列表缓存区域版本号
        /// </summary>
        /// <param name="propertyName">分区属性名称</param>
        /// <param name="propertyValue">分区属性值</param>
        /// <returns>分区属性的缓存版本（从0开始）</returns>
        public async Task<int> GetAreaVersionAsync(string? propertyName, object? propertyValue)
        {
            var entityCache = await GetEntityCacheAsync();
            int num = 0;
            if (!string.IsNullOrEmpty(propertyName) && propertyValue != null)
            {
                propertyName = propertyName.ToLower();
                lock (entityCache.AreaVersionDictionary)
                {
                    if (entityCache.AreaVersionDictionary.TryGetValue(propertyName, out Dictionary<int, int>? dictionary))
                    {
                        dictionary.TryGetValue(propertyValue.GetHashCode(), out num);
                    }
                }
            }
            return num;
        }

        /// <summary>
        /// 获取实体缓存的cacheKey 
        /// </summary>
        /// <param name="primaryKey">主键</param>
        /// <returns>实体的CacheKey</returns>
        public string GetCacheKeyOfEntity(object primaryKey)
        {
            var entityCache = GetEntityCache();
            if (EnableDistributedCache)
            {
                return string.Concat(new object[] { entityCachePrefix, ":", entityCache.TypeHashID, ":", primaryKey, ":", this.GetEntityVersion(primaryKey) });
            }
            return $"{entityCachePrefix}:{entityCache.TypeHashID}:{primaryKey}";
        }

        /// <summary>
        /// 获取实体缓存的cacheKey 
        /// </summary>
        /// <param name="primaryKey">主键</param>
        /// <returns>实体的CacheKey</returns>
        public async Task<string> GetCacheKeyOfEntityAsync(object primaryKey)
        {
            var entityCache = await GetEntityCacheAsync();
            if (EnableDistributedCache)
            {
                return string.Concat(new object[] { entityCachePrefix, ":", entityCache.TypeHashID, ":", primaryKey, ":", await this.GetEntityVersionAsync(primaryKey) });
            }
            return $"{entityCachePrefix}:{entityCache.TypeHashID}:{primaryKey}";
        }

        /// <summary>
        /// 获取实体正文缓存的cacheKey 
        /// </summary>
        /// <param name="primaryKey">主键</param>
        /// <returns>实体正文缓存的cacheKey</returns>
        public string GetCacheKeyOfEntityBody(object primaryKey)
        {
            var entityCache = GetEntityCache();
            if (EnableDistributedCache)
            {
                return string.Concat(new object[] { entityCachePrefix, ":", entityCache.TypeHashID, ":B-", primaryKey, ":", this.GetEntityVersion(primaryKey) });
            }
            return $"{entityCachePrefix}:{entityCache.TypeHashID}:{primaryKey}";
        }


        /// <summary>
        /// 获取实体正文缓存的cacheKey 
        /// </summary>
        /// <param name="primaryKey">主键</param>
        /// <returns>实体正文缓存的cacheKey</returns>
        public async Task<string> GetCacheKeyOfEntityBodyAsync(object primaryKey)
        {
            var entityCache = await GetEntityCacheAsync();
            if (EnableDistributedCache)
            {
                return string.Concat(new object[] { entityCachePrefix, ":", entityCache.TypeHashID, ":B-", primaryKey, ":", await this.GetEntityVersionAsync(primaryKey) });
            }
            return $"{entityCachePrefix}:{entityCache.TypeHashID}:{primaryKey}";
        }

        /// <summary>
        /// EntityCache的CacheKey 
        /// </summary>
        /// <param name="typeHashID"></param>
        /// <returns>typeHashID对应类型的缓存设置CacheKey</returns>
        internal static string GetCacheKeyOfEntityCache(string typeHashID)
        {
            return ("EntityCacheVersion:" + typeHashID);
        }

        /// <summary>
        /// 获取Entity的缓存版本 
        /// </summary>
        /// <param name="primaryKey">实体主键</param>
        /// <returns>实体的缓存版本（从0开始）</returns>
        public int GetEntityVersion(object primaryKey)
        {
            if (primaryKey == null)
                throw new ArgumentNullException(nameof(primaryKey));
            int num = 0;
            var entityCache = GetEntityCache();
            lock (entityCache.EntityVersionDictionary)
                entityCache.EntityVersionDictionary.TryGetValue($"{primaryKey}", out num);
            return num;
        }

        /// <summary>
        /// 获取Entity的缓存版本 
        /// </summary>
        /// <param name="primaryKey">实体主键</param>
        /// <returns>实体的缓存版本（从0开始）</returns>
        public async Task<int> GetEntityVersionAsync(object primaryKey)
        {
            if (primaryKey == null)
                throw new ArgumentNullException(nameof(primaryKey));
            int num = 0;
            var entityCache = await GetEntityCacheAsync();
            lock (entityCache.EntityVersionDictionary)
                entityCache.EntityVersionDictionary.TryGetValue($"{primaryKey}", out num);
            return num;
        }

        /// <summary>
        /// 列表缓存全局version 
        /// </summary>
        /// <returns></returns>
        public int GetGlobalVersion()
        {
            return GetEntityCache().GlobalVersion;
        }

        /// <summary>
        /// 获取列表缓存CacheKey的前缀（例如：abe3ds2sa90:8:） 
        /// </summary>
        /// <param name="cacheVersionType">列表缓存版本设置</param>
        /// <returns>列表缓存CacheKey的前缀</returns>
        public string GetListCacheKeyPrefix(CacheVersionType cacheVersionType)
        {
            return this.GetListCacheKeyPrefix(cacheVersionType, null, null);
        }

        /// <summary>
        /// 获取列表缓存CacheKey的前缀（例如：abe3ds2sa90:8:） 
        /// </summary>
        /// <param name="cacheVersionType">列表缓存版本设置</param>
        /// <returns>列表缓存CacheKey的前缀</returns>
        public async Task<string> GetListCacheKeyPrefixAsync(CacheVersionType cacheVersionType)
        {
            return await this.GetListCacheKeyPrefixAsync(cacheVersionType, null, null);
        }

        /// <summary>
        /// 获取列表缓存CacheKey的前缀（例如：abe3ds2sa90:8:） 
        /// </summary>
        /// <param name="cacheVersionSetting">列表缓存设置</param>
        /// <returns>列表缓存CacheKey的前缀</returns>
        public string GetListCacheKeyPrefix(IListCacheSetting cacheVersionSetting)
        {
            var entityCache = GetEntityCache();
            StringBuilder builder = new StringBuilder(entityCache.TypeHashID);
            builder.Append("-L:");
            switch (cacheVersionSetting.CacheVersionType)
            {
                case CacheVersionType.GlobalVersion:
                    builder.AppendFormat("{0}:", this.GetGlobalVersion());
                    break;

                case CacheVersionType.AreaVersion:
                    builder.AppendFormat("{0}-{1}-{2}:", cacheVersionSetting.AreaCachePropertyName, cacheVersionSetting.AreaCachePropertyValue.ToString(),
                        this.GetAreaVersion(cacheVersionSetting.AreaCachePropertyName, cacheVersionSetting.AreaCachePropertyValue));
                    break;
                default:
                    break;
            }
            return builder.ToString();
        }

        /// <summary>
        /// 获取列表缓存CacheKey的前缀（例如：abe3ds2sa90:8:） 
        /// </summary>
        /// <param name="cacheVersionSetting">列表缓存设置</param>
        /// <returns>列表缓存CacheKey的前缀</returns>
        public async Task<string> GetListCacheKeyPrefixAsync(IListCacheSetting cacheVersionSetting)
        {
            var entityCache = await GetEntityCacheAsync();
            StringBuilder builder = new StringBuilder(entityCache.TypeHashID);
            builder.Append("-L:");
            switch (cacheVersionSetting.CacheVersionType)
            {
                case CacheVersionType.GlobalVersion:
                    builder.AppendFormat("{0}:", this.GetGlobalVersion());
                    break;

                case CacheVersionType.AreaVersion:
                    builder.AppendFormat("{0}-{1}-{2}:", cacheVersionSetting.AreaCachePropertyName, cacheVersionSetting.AreaCachePropertyValue.ToString(), await this.GetAreaVersionAsync(cacheVersionSetting.AreaCachePropertyName, cacheVersionSetting.AreaCachePropertyValue));
                    break;
                default:
                    break;
            }
            return builder.ToString();
        }

        /// <summary>
        /// 获取列表缓存CacheKey的前缀（例如：abe3ds2sa90:8:） 
        /// </summary>
        /// <param name="cacheVersionType"></param>
        /// <param name="areaCachePropertyName">缓存分区名称</param>
        /// <param name="areaCachePropertyValue">缓存分区值</param>
        /// <returns></returns>
        public string GetListCacheKeyPrefix(CacheVersionType cacheVersionType, string? areaCachePropertyName, object? areaCachePropertyValue)
        {
            var entityCache = GetEntityCache();
            StringBuilder builder = new StringBuilder(entityCache.TypeHashID);
            builder.Append("-L:");
            switch (cacheVersionType)
            {
                case CacheVersionType.GlobalVersion:
                    builder.AppendFormat("{0}:", this.GetGlobalVersion());
                    break;

                case CacheVersionType.AreaVersion:
                    builder.AppendFormat("{0}-{1}-{2}:", areaCachePropertyName, areaCachePropertyValue, this.GetAreaVersion(areaCachePropertyName, areaCachePropertyValue));
                    break;
                default:
                    break;
            }
            return builder.ToString();
        }

        /// <summary>
        /// 获取列表缓存CacheKey的前缀（例如：abe3ds2sa90:8:） 
        /// </summary>
        /// <param name="cacheVersionType"></param>
        /// <param name="areaCachePropertyName">缓存分区名称</param>
        /// <param name="areaCachePropertyValue">缓存分区值</param>
        /// <returns></returns>
        public async Task<string> GetListCacheKeyPrefixAsync(CacheVersionType cacheVersionType, string? areaCachePropertyName, object? areaCachePropertyValue)
        {
            var entityCache = await GetEntityCacheAsync();
            StringBuilder builder = new StringBuilder(entityCache.TypeHashID);
            builder.Append("-L:");
            switch (cacheVersionType)
            {
                case CacheVersionType.GlobalVersion:
                    builder.AppendFormat("{0}:", this.GetGlobalVersion());
                    break;

                case CacheVersionType.AreaVersion:
                    builder.AppendFormat("{0}-{1}-{2}:", areaCachePropertyName, areaCachePropertyValue, await this.GetAreaVersionAsync(areaCachePropertyName, areaCachePropertyValue));
                    break;
                default:
                    break;
            }
            return builder.ToString();
        }

        /// <summary>
        /// 递增列表缓存区域version 
        /// </summary>
        /// <param name="propertyName">分区属性名称</param>
        /// <param name="propertyValues">多个分区属性值</param>
        public void IncreaseAreaVersion(EntityCache entityCache, string propertyName, IEnumerable<object> propertyValues)
        {
            this.IncreaseAreaVersion(entityCache, propertyName, propertyValues, true);
        }

        /// <summary>
        /// 递增列表缓存区域version
        /// </summary>
        /// <param name="propertyName">分区属性名称</param>
        /// <param name="propertyValue">分区属性值</param>
        public void IncreaseAreaVersion(EntityCache entityCache, string propertyName, object propertyValue)
        {
            if (propertyValue != null)
            {
                this.IncreaseAreaVersion(entityCache, propertyName, new object[] { propertyValue }, true);
            }
        }

        /// <summary>
        /// 递增列表缓存区域version 
        /// </summary>
        /// <param name="propertyName">分区属性名称</param>
        /// <param name="propertyValues">多个分区属性值</param>
        /// <param name="raiseChangeEvent">是否触发Change事件</param>
        private void IncreaseAreaVersion(EntityCache entityCache, string propertyName, IEnumerable<object> propertyValues, bool raiseChangeEvent)
        {
            if (!string.IsNullOrEmpty(propertyName))
            {
                propertyName = propertyName.ToLower();
                int num = 0;
                lock (entityCache.AreaVersionDictionary)
                {
                    if (!entityCache.AreaVersionDictionary.TryGetValue(propertyName, out Dictionary<int, int>? dictionary))
                    {
                        entityCache.AreaVersionDictionary[propertyName] = new Dictionary<int, int>();
                        dictionary = entityCache.AreaVersionDictionary[propertyName];
                    }

                    foreach (object propertyVal in propertyValues)
                    {
                        int hashCode = propertyVal.GetHashCode();
                        if (dictionary.TryGetValue(hashCode, out num))
                        {
                            num++;
                        }
                        else
                        {
                            num = 1;
                        }
                        dictionary[hashCode] = num;
                    }
                }
                if (raiseChangeEvent)
                {
                    this.OnChanged(entityCache);
                }
            }
        }

        /// <summary>
        /// 递增实体缓存（仅更新实体时需要递增）
        /// </summary>
        /// <param name="entityId">实体Id</param>
        public void IncreaseEntityCacheVersion(EntityCache entityCache, object entityId)
        {
            if (entityId == null)
                throw new ArgumentNullException(nameof(entityId));

            if (EnableDistributedCache)
            {
                int num;
                lock (entityCache.EntityVersionDictionary)
                {
                    if (entityCache.EntityVersionDictionary.TryGetValue($"{entityId}", out num))
                    {
                        num++;
                    }
                    else
                    {
                        num = 1;
                    }
                    entityCache.EntityVersionDictionary[$"{entityId}"] = num;
                }
                this.OnChanged(entityCache);
            }
        }

        /// <summary>
        /// 递增列表缓存全局版本 
        /// </summary>
        public void IncreaseGlobalVersion(EntityCache entityCache)
        {
            lock (lockObj)
            {
                entityCache.GlobalVersion++;
            }
        }

        /// <summary>
        /// 递增列表缓存version（增加、更改、删除实体时需要递增）
        /// </summary>
        /// <param name="entity"></param>
        public void IncreaseListCacheVersion(EntityCache entityCache, IEntity entity)
        {
            if (entityCache.PropertiesOfArea != null)
            {
                foreach (EntityPropertyInfo info in entityCache.PropertiesOfArea)
                {
                    object? obj = info.GetValue(entity);
                    if (obj != null)
                    {
                        this.IncreaseAreaVersion(entityCache, info.Name.ToLower(), new object[] { obj }, false);
                    }
                }
            }
            this.IncreaseGlobalVersion(entityCache);
            this.OnChanged(entityCache);
        }

        /// <summary>
        /// 标识为已删除
        /// </summary>
        /// <param name="entity"></param>
        public async Task MarkDeletionAsync(EntityCache entityCache, IEntity entity)
        {
            if (entityCache.EnableCache)
            {
                cacheService.MarkDeletion(await this.GetCacheKeyOfEntityAsync(entity.EntityId), entity, CachingExpirationType.SingleObject);
            }
        }

        /// <summary>
        /// 对象变更时回调 
        /// 在分布式缓存情况，需要把缓存设置存储到缓存中 
        /// </summary>
        private void OnChanged(EntityCache entityCache)
        {
            if (EnableDistributedCache)
            {
                cacheService.Set(GetCacheKeyOfEntityCache(entityCache.TypeHashID), entityCache, CachingExpirationType.Invariable);
            }
        }


    }
}
